package org.dromara.third.auth.util;

import org.apache.commons.codec.binary.Base64;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Map;
import java.util.TreeMap;

/**
 * 签名工具类
 *
 * @author admin
 * @date 2020/7/13 15:04
 */
public class SignatureUtils {

    public static final String DMALL_HEAD_ALGORITHM = "Algorithm";
    public static final String DMALL_ACCESSKEY_ID = "AccessKeyId";
    public static final String DMALL_HEAD_SIGNATURE = "Signature";
    public static final String DMALL_TIMESTAMP = "TimeStamp";
    public static final String UTF_8 = "UTF-8";
    public static void main(String[] args) {
        String appId = "821537580880232448";
        String appSecret = "d59cae67e41ac33599f4";

        String timestamp = String.valueOf(System.currentTimeMillis());
        String queryString = "grantType=client_credential&appId=" + appId + "&timestamp=" + timestamp;

        String sign = getAuthorization(appId, appSecret, queryString, "GET", timestamp);
        System.out.println(sign);
    }
    /**
     * 获取时间戳
     */
    public static String getUTCTime() {
        // 1、取得本地时间：
        Calendar cal = Calendar.getInstance();
        // 2、取得时间偏移量：
        int zoneOffset = cal.get(Calendar.ZONE_OFFSET);
        // 3、取得夏令时差：
        int dstOffset = cal.get(Calendar.DST_OFFSET);
        // 4、从本地时间里扣除这些差量，即可以取得UTC时间：
        cal.add(Calendar.MILLISECOND, -(zoneOffset + dstOffset));
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return sdf.format(cal.getTime());
    }

    /**
     * 签名计算方法
     *
     * @param accessKeyId     密钥id
     * @param accessKeySecret 密钥secret
     * @param httpMethod      http请求方法类型，目前支持 POST, PUT, DELETE, GET四种
     * @param queryString     http请求参数，通常为?号之后的内容
     * @param timeStamp       时间戳, 格式 yyyy-MM-dd HH:mm:ss，注意时区为UTC时区，如何获取utc时间，参看getUTCTime方法
     *                        注意：为防止重放，调用方传入的时间与服务器时间不能偏差超过5分钟
     * @return 计算结果，该字段为String类型，上层需要把该字段放到http header中，其中key为Authorization，value为返回结果
     */
    public static String getAuthorization(String accessKeyId, String accessKeySecret, String queryString, String httpMethod, String timeStamp) {
        /* 构造计算签名的数据 */
        String signature = getSignString(queryString, httpMethod, timeStamp, accessKeySecret);
        if (null == signature) {
            return null;
        }
        StringBuilder auth = new StringBuilder();
        auth.append(DMALL_HEAD_ALGORITHM).append("=").append("HMAC-SHA256").append(",")
                .append(DMALL_ACCESSKEY_ID).append("=").append(accessKeyId).append(",")
                .append(DMALL_TIMESTAMP).append("=").append(timeStamp).append(",")
                .append(DMALL_HEAD_SIGNATURE).append("=").append(signature);
        return auth.toString();
    }

    private static String getSignString(String queryString, String httpMethod, String timeStamp, String accessKeySecret) {
        String strToSign = null;
        try {
            if (null != queryString) {
                queryString = URLDecoder.decode(queryString, UTF_8);
            }
            strToSign = buildCanonicalString(queryString, httpMethod, timeStamp);
        } catch (UnsupportedEncodingException e) {
            return strToSign;
        }
        return signWithHmac(strToSign, accessKeySecret);
    }

    private static String buildCanonicalString(String parameters, String httpMethod, String timeStamp) {
        StringBuilder builder = new StringBuilder();
        builder.append(httpMethod).append("&");
        try {
            builder.append(URLEncoder.encode("/", UTF_8)).append("&");
            if (null != timeStamp) {
                builder.append(URLEncoder.encode(timeStamp, UTF_8)).append("&");
            }
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        if (null != parameters) {
            String queryString = parameters;
            // 去除#后面部分
            int fragIdx = parameters.indexOf('#');
            if (fragIdx >= 0) {
                queryString = parameters.substring(0, fragIdx);
            }
            builder.append(buildqueryString(queryString));
        }
        return builder.toString();
    }

    private static String buildqueryString(String queryString) {
        Map<String, String> params = getQueryStringMap(queryString);
        StringBuilder builder = new StringBuilder("");
        if (params.size() > 0) {
            String[] names = params.keySet().toArray(new String[params.size()]);
            Arrays.sort(names);
            char separator = '&';
            for (int i = 0; i < names.length; i++) {
                String name = names[i];
                if (0 != i) {
                    builder.append(separator);
                }
                builder.append(name);
                String paramValue = params.get(name);
                if (paramValue != null && paramValue.length() > 0) {
                    builder.append("=").append(paramValue);
                }
            }
        }
        String cononiStr = "";
        try {
            cononiStr = URLEncoder.encode(builder.toString(), UTF_8);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return cononiStr;
    }

    private static Map<String, String> getQueryStringMap(String queryString) {
        Map<String, String> params = new TreeMap<String, String>();
        if (null != queryString) {
            String[] splits = queryString.split("&");
            for (int i = 0; i < splits.length; i++) {
                String query = splits[i];
                String[] tmpSplit = query.split("=");
                if (tmpSplit.length >= 2) {
                    String name = tmpSplit[0];
                    String value = tmpSplit[1];
                    params.put(name, value);
                }
            }
        }
        return params;
    }

    private static byte[] hmacsha256Signature(byte[] data, byte[] key) {
        try {
            SecretKeySpec signingKey = new SecretKeySpec(key, "HmacSHA256");
            Mac mac = Mac.getInstance("HmacSHA256");
            mac.init(signingKey);
            return mac.doFinal(data);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    private static String signWithHmac(String strToSign, String accessKey) {
        byte[] crypto = hmacsha256Signature(strToSign.getBytes(), accessKey.getBytes());
        String signature = Base64.encodeBase64String(crypto).trim();
        return signature;
    }
}
